/*
 * Copyright 2001 Alexander Boverman and the original authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <tcl.h>
#include "mod_wtcl.h"
#include "WtContext.h"
#include "WtUtil.h"
#include "WtTable.h"
#include "WtTableUtil.h"
#include "WtSettings.h"

module MODULE_VAR_EXPORT wtcl_module;

static const char *wtEventsKey = "server.ctxEventHandlers";

static int wtcl_handler(request_rec *r)
{
  return WtProcessRequest(r);
}

static void wtcl_init(server_rec *s, pool *p)
{
  ap_log_error(HERE, APLOG_DEBUG | APLOG_NOERRNO, s, "Wtcl: wtcl_init");
}

static int WtCfgMergeEvents(Tcl_Obj *conf, Tcl_Obj *base, Tcl_Obj *overrides)
{
  int ok = 1;
  Tcl_Obj *confEvt = WtTableGetObjFromStr(conf, wtEventsKey, NULL);
  Tcl_Obj *baseEvt = WtTableGetObjFromStr(base, wtEventsKey, NULL);
  Tcl_Obj *overridesEvt = WtTableGetObjFromStr(overrides, wtEventsKey, NULL);
  Tcl_Obj *name, *item;
  Tcl_HashTable *hashTable;
  Tcl_HashEntry *ent;
  Tcl_HashSearch search;

  if (baseEvt && !overridesEvt) {
    WtTableSetStrToObj(conf, wtEventsKey, baseEvt);
  } else if (!baseEvt && overridesEvt) {
    WtTableSetStrToObj(conf, wtEventsKey, overridesEvt);
  } else {
    if (!confEvt) {
      confEvt = WtNewTableObj();
      WtTableSetStrToObj(conf, wtEventsKey, confEvt);
    }

    hashTable = WtGetTableMap(confEvt);
    ent = Tcl_FirstHashEntry(hashTable, &search);

    while (ent) {
      name = (Tcl_Obj *)Tcl_GetHashKey(hashTable, ent);
      item = (Tcl_Obj *)Tcl_GetHashValue(ent);
      if (!WtTableGetIntFromStr(item, "add", 0)) {
        WtTableRemove(confEvt, name);
      }
      if (!WtTableAppendToList(confEvt, name, item, NULL, NULL)) {
        ok = 0;
        break;
      }
      ent = Tcl_NextHashEntry(&search);
    }
  }

  return ok;
}

static void *wtcl_create_dir_config(pool *p, char *path)
{
  return WtNewTableObj();
}

static void *wtcl_merge_dir_config(pool *p, void *basev, void *overridesv)
{
  int ok = 1;
  Tcl_Obj *conf = WtNewTableObj();
  Tcl_Obj *base = (Tcl_Obj *)basev;
  Tcl_Obj *overrides = (Tcl_Obj *)overridesv;

  WtUpdateTable(conf, base);
  WtUpdateTable(conf, overrides);

  if (!WtCfgMergeEvents(conf, base, overrides)) {
    ok = 0;
    Tcl_DecrRefCount(conf);
    conf = NULL;
  }

  return conf;
}

static void *wtcl_create_server_config(pool *p, server_rec *s)
{
  ap_log_error(HERE, APLOG_DEBUG | APLOG_NOERRNO, s,
    "Wtcl: wtcl_create_server_config: pid = %d, tid = %d.",
    getpid(), gettid());

  return WtNewTableObj();
}

static void *wtcl_merge_server_config(pool *p, void *basev, void *overridesv)
{
  int ok = 1;
  Tcl_Obj *conf = WtNewTableObj();
  Tcl_Obj *base = (Tcl_Obj *)basev;
  Tcl_Obj *overrides = (Tcl_Obj *)overridesv;

  WtUpdateTable(conf, base);
  WtUpdateTable(conf, overrides);

  if (!WtCfgMergeEvents(conf, base, overrides)) {
    ok = 0;
    Tcl_DecrRefCount(conf);
    conf = NULL;
  }

  return conf;
}

static void wtcl_child_init(server_rec *s, pool *p)
{
  ap_log_error(HERE, APLOG_DEBUG | APLOG_NOERRNO, s,
    "Wtcl: wtcl_child_init: pid = %d, tid = %d.",
    getpid(), gettid());

  WtInitServer(s, p);
}

static void wtcl_child_exit(server_rec *s, pool *p)
{
  ap_log_error(HERE, APLOG_DEBUG | APLOG_NOERRNO, s, "Wtcl: wtcl_child_exit"); 
  WtEndServer(s, p);
}

Tcl_Obj *WtGetTargetConf(cmd_parms *cmd, void *dirConf)
{
  return cmd->path ? dirConf : ap_get_module_config(
    cmd->server->module_config, &wtcl_module);
}

static const char *WtReuseInterpCfg(cmd_parms *cmd, void *dirConf, int flag)
{
  WtTableSetStrToBool(WtGetTargetConf(cmd, dirConf),
    "server.reuseInterp", flag);
  return NULL;
}

static const char *WtDeleteInterpCfg(cmd_parms *cmd, void *dirConf, int flag)
{
  WtTableSetStrToBool(WtGetTargetConf(cmd, dirConf),
    "server.deleteInterp", flag);
  return NULL;
}

static const char *WtTaskNamespaceCfg(cmd_parms *cmd, void *dirConf, char *script)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "server.taskNamespace", script);
  return NULL;
}

static Tcl_Obj *WtCfgGetEvents(Tcl_Obj *tbl)
{
  Tcl_Obj *events = WtTableGetObjFromStr(tbl, wtEventsKey, NULL);
  if (!events) {
    events = WtNewTableObj();
    WtTableSetStrToObj(tbl, wtEventsKey, events);
  }
  return events;
}

static const char *WtEventHandlerCfg(cmd_parms *cmd, void *dirConf,
    char *a, char *b, char *c)
{
  char *ret = NULL, *mode, *name, *script;
  Tcl_Obj *nameObj, *scriptObj, *tbl, *events, *item;
  int doAdd = 0;

  mode = c ? a : NULL;
  name = c ? b : a;
  nameObj = WtNewString(name);
  Tcl_IncrRefCount(nameObj);
  script = c ? c : b;
  scriptObj = WtNewString(script);
  Tcl_IncrRefCount(scriptObj);

  tbl = WtGetTargetConf(cmd, dirConf);

  if (mode) {
    if (!strcmp(mode, "add")) {
      doAdd = 1;
    } else {
      ret = "WtEventHandler: Invalid option.";
    }
  }

  if (!mode || doAdd) {
    events = WtCfgGetEvents(tbl);
    if (!doAdd) {
      WtTableRemove(events, nameObj);
    }
    item = WtNewTableObj();
    WtTableSetStrToInt(item, "add", doAdd);
    WtTableSetStrToObj(item, "script", scriptObj);
    if (!WtTableAppendToList(events, nameObj, item, NULL, NULL)) {
      ret = "WtEventHandler: Failed to set configuration value.";
      Tcl_DecrRefCount(item);
    }
  }

  Tcl_DecrRefCount(nameObj);
  Tcl_DecrRefCount(scriptObj);

  return ret;
}

static const char *WtIncludeNamespaceCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "server.includeNamespace", arg1);
  return NULL;
}

static const char *WtAddHeaderTrailerCfg(cmd_parms *cmd, void *dirConf,
    int flag)
{
  WtTableSetStrToBool(WtGetTargetConf(cmd, dirConf),
    "server.addHeaderTrailer", flag);
  return NULL;
}

static const char *WtIncludeHeaderCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "server.includeHeader", arg1);
  return NULL;
}

static const char *WtIncludeTrailerCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "server.includeTrailer", arg1);
  return NULL;
}

static const char *WtScriptStartStringCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "server.scriptStartString", arg1);
  return NULL;
}

static const char *WtScriptEndStringCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "server.scriptEndString", arg1);
  return NULL;
}

static const char *WtInterpolateTextCfg(cmd_parms *cmd, void *dirConf,
    int flag)
{
  WtTableSetStrToBool(WtGetTargetConf(cmd, dirConf),
    "server.interpolateText", flag);
  return NULL;
}

static const char *WtPageTextHandlerCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "server.pageTextHandler", arg1);
  return NULL;
}

static const char *WtPageExprHandlerCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "server.pageExprHandler", arg1);
  return NULL;
}

static const char *WtSessionHandlerCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "session.handler", arg1);
  return NULL;
}

static const char *WtSessionHandlerTypeCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "session.handlerType", arg1);
  return NULL;
}

static const char *WtSessionCookieNameCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "session.cookieName", arg1);
  return NULL;
}

static const char *WtSessionMaxIdleTimeCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  char *err = NULL;
  Tcl_Obj *numStr;
  int intVal;

  numStr = WtNewString(arg1);
  Tcl_IncrRefCount(numStr);

  if (Tcl_GetIntFromObj(NULL, numStr, &intVal) != TCL_OK) {
    err = "WtSessionMaxIdleTime: Number of seconds must be an integer.";
  } else {
    WtTableSetStrToInt(WtGetTargetConf(cmd, dirConf),
      "session.maxIdleTime", intVal);
  }

  return err;
}

static const char *WtSessionSweepFrequencyCfg(cmd_parms *cmd,
    void *dirConf, char *arg1)
{
  char *err = NULL;
  Tcl_Obj *numStr;
  double dblVal;

  numStr = WtNewString(arg1);
  Tcl_IncrRefCount(numStr);

  if (Tcl_GetDoubleFromObj(NULL, numStr, &dblVal) != TCL_OK) {
    err = "WtSessionSweepFrequency: Value must be a decimal number.";
  } else {
    WtTableSetStrToDouble(WtGetTargetConf(cmd, dirConf),
      "session.sweepFrequency", dblVal);
  }

  return err;
}

static const char *WtDbSessionHandlerCfg(cmd_parms *cmd, void *dirConf,
    char *arg1)
{
  WtTableSetStrToStr(WtGetTargetConf(cmd, dirConf),
    "session.dbSession.dbHandlerNamespace", arg1);
  return NULL;
}

static const handler_rec wtcl_handlers[] =
{
  {"application/x-httpd-wtcl", wtcl_handler},
  {NULL}
};

static const command_rec wtcl_cmds[] =
{
  {"WtReuseInterp", WtReuseInterpCfg, NULL, OR_FILEINFO, FLAG,
    "Reuse the same interpreter for each request. Usage: WtReuseInterp flag"},

  {"WtDeleteInterp", WtDeleteInterpCfg, NULL, OR_FILEINFO, FLAG,
    "Delete the interpreter when the request ends. "
    "Usage: WtDeleteInterp flag"},

  {"WtTaskNamespace", WtTaskNamespaceCfg, NULL, OR_FILEINFO, FLAG,
    "Evaluate the request in this namespace. Usage: WtTaskNamespace namespace"},

  {"WtEventHandler", WtEventHandlerCfg, NULL, OR_FILEINFO, TAKE23,
    "Script to evaluate when a context event is triggered. "
    "The script will replace any previously defined handlers unless "
    "the \"add\" option is specified. "
    "Usage: WtEventHandler [add] eventName script"},

  {"WtIncludeNamespace", WtIncludeNamespaceCfg, NULL, ACCESS_CONF, TAKE1,
    "Evaluate includes in this namespace. "
    "Usage: WtIncludeNamespace namespace"},

  {"WtAddHeaderTrailer", WtAddHeaderTrailerCfg, NULL, ACCESS_CONF, FLAG,
    "Add header and trailer strings to includes. "
    "Usage: WtAddHeaderTrailer flag"},

  {"WtIncludeHeader", WtIncludeHeaderCfg, NULL, ACCESS_CONF, TAKE1,
    "Header to prepend to includes. Usage: WtIncludeHeader string"},

  {"WtIncludeTrailer", WtIncludeTrailerCfg, NULL, ACCESS_CONF, TAKE1,
    "Trailer to append to includes. Usage: WtIncludeTrailer string"},

  {"WtScriptStartString", WtScriptStartStringCfg, NULL, ACCESS_CONF, TAKE1,
    "Token marking the beginning of a script section. "
    "Usage: WtScriptStartString string"},

  {"WtScriptEndString", WtScriptEndStringCfg, NULL, ACCESS_CONF, TAKE1,
    "Token marking the end of a script section. "
    "Usage: WtScriptEndString string"},

  {"WtInterpolateText", WtInterpolateTextCfg, NULL, ACCESS_CONF, FLAG,
    "Interpolate text sections with subst prior to printing. "
    "Usage: WtInterpolateText flag."},

  {"WtPageTextHandlerString", WtPageTextHandlerCfg, NULL, ACCESS_CONF, TAKE1,
    "Handler for text sections. Usage: WtPageTextHandler proc"},

  {"WtPageExprHandlerString", WtPageExprHandlerCfg, NULL, ACCESS_CONF,
    TAKE1, "Handler for page expressions. "
    "Usage: WtPageExprResultHandler proc"},

  {"WtSessionHandler", WtSessionHandlerCfg, NULL, ACCESS_CONF, TAKE1,
    "Session handler. Usage: WtSessionHandler script"},

  {"WtSessionHandlerType", WtSessionHandlerTypeCfg, NULL, ACCESS_CONF, TAKE1,
    "Session handler type. Usage: WtSessionHandlerType type"},

  {"WtSessionCookieName", WtSessionCookieNameCfg, NULL, ACCESS_CONF, TAKE1,
    "Session handler cookie name. Usage: WtSessionCookieName name"},

  {"WtSessionMaxIdleTime", WtSessionMaxIdleTimeCfg, NULL, ACCESS_CONF, TAKE1,
    "Maximum idle time in seconds. Usage: WtSessionMaxIdleTime seconds"},

  {"WtSessionSweepFrequency", WtSessionSweepFrequencyCfg, NULL,
    ACCESS_CONF, TAKE1,
    "Value between 0 and 1. The probability that expired sessions "
    " will be expunged at the end of a request. "
    "Usage: WtSessionSweepFrequency frequency"},

  {"WtDbSessionHandler", WtDbSessionHandlerCfg, NULL, ACCESS_CONF, TAKE1,
    "Database session storage handler. "
    "Usage: WtDbSessionHandler handler"},

  {NULL}
};

#ifdef WIN32

static DWORD tls_index;

BOOL WINAPI DllMain(HINSTANCE dllhandle, DWORD reason, LPVOID reserved)
{
  switch (reason) {
    case DLL_PROCESS_ATTACH:
      tls_index = TlsAlloc();
      if (tls_index == TLS_OUT_OF_INDEXES) {
        return FALSE;
      }
    case DLL_THREAD_ATTACH:
      TlsSetValue(tls_index, (LPVOID)0);
      break;
    case DLL_THREAD_DETACH:
      break;
    case DLL_PROCESS_DETACH:
      TlsFree(tls_index);
      break;
  }

  return TRUE;
}

WtThreadStorage *WtGetThreadStorage()
{
  WtThreadStorage *p = (WtThreadStorage *)TlsGetValue(tls_index);
  if (!p) {
    p = (WtThreadStorage *)calloc(1, sizeof(WtThreadStorage));
    TlsSetValue(tls_index, p);
  }
  return p;
}

#endif /* WIN32 */

module MODULE_VAR_EXPORT wtcl_module =
{
  STANDARD_MODULE_STUFF,
  wtcl_init,                 /* module initializer */
  wtcl_create_dir_config,
  wtcl_merge_dir_config,
  wtcl_create_server_config,
  wtcl_merge_server_config,
  wtcl_cmds,
  wtcl_handlers,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
#if MODULE_MAGIC_NUMBER >= 19970103
  NULL,
#endif
#if MODULE_MAGIC_NUMBER >= 19970719
  wtcl_child_init,         /* process initializer */
#endif
#if MODULE_MAGIC_NUMBER >= 19970728
  wtcl_child_exit,         /* process exit/cleanup */
#endif
#if MODULE_MAGIC_NUMBER >= 19970902
  NULL
#endif
};
